package com.encryption.service.ca.impl;

import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import com.encryption.service.ca.PKCS12CertificateGenerator;
import com.encryption.service.ca.util.CAConstants;
import com.encryption.service.ca.util.CryptoUtil;
import com.encryption.service.ca.util.PropertyLoader;

public class PKCS12CertificateGeneratorImpl implements PKCS12CertificateGenerator {
     X509V1CertificateGenerator v1CertGen = new X509V1CertificateGenerator();
     X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
  
    /**
     * we generate the CA's certificate
     */
    private Certificate createMasterCert(PublicKey pubKey, PrivateKey privKey)
                                                                                    throws Exception {
        String issuer = "C=CN, O=Land of promise, OU=LOP";
        String subject = "C=CN, O=Land of promise, OU=LOP";


        v1CertGen.setSerialNumber(BigInteger.valueOf(1));
        v1CertGen.setIssuerDN(new X509Principal(issuer));
        v1CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30));
        v1CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)));
        v1CertGen.setSubjectDN(new X509Principal(subject));
        v1CertGen.setPublicKey(pubKey);
        v1CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        X509Certificate cert = v1CertGen.generateX509Certificate(privKey);

        cert.checkValidity(new Date());

        cert.verify(pubKey);

        PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;

        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(
            "Bouncy Primary Certificate"));

        return cert;
    }
    
    /**
     * we generate a certificate signed by our CA's intermediate certficate
     */
    public Certificate createCert(PublicKey pubKey, PrivateKey caPrivKey, PublicKey caPubKey,
                                  String deptId, String deptName)
                                                                                                    throws Exception {
        //
        // signers name table.
        //
        Hashtable sAttrs = new Hashtable();
        Vector sOrder = new Vector();

        sAttrs.put(X509Principal.C, "CN");
        sAttrs.put(X509Principal.O, "Land of Promise");
        sAttrs.put(X509Principal.OU, "zxzj");
        sAttrs.put(X509Principal.EmailAddress, "landofpromise@163.com");

        sOrder.addElement(X509Principal.C);
        sOrder.addElement(X509Principal.O);
        sOrder.addElement(X509Principal.OU);
        sOrder.addElement(X509Principal.EmailAddress);

        //
        // subjects name table.
        //
        Hashtable attrs = new Hashtable();
        Vector order = new Vector();

        attrs.put(X509Principal.C, "CN");
        attrs.put(X509Principal.O, "Land of promise");
        attrs.put(X509Principal.L, deptId);
        attrs.put(X509Principal.CN, deptName);
        attrs.put(X509Principal.EmailAddress, "resource@lop.com");

        order.addElement(X509Principal.C);
        order.addElement(X509Principal.O);
        order.addElement(X509Principal.L);
        order.addElement(X509Principal.CN);
        order.addElement(X509Principal.EmailAddress);

        //
        // create the certificate - version 3
        //
        v3CertGen.reset();

        v3CertGen.setSerialNumber(BigInteger.valueOf(Integer.valueOf(deptId)));
        v3CertGen.setIssuerDN(new X509Principal(sOrder, sAttrs));
        v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30));
        v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)));
        v3CertGen.setSubjectDN(new X509Principal(order, attrs));
        v3CertGen.setPublicKey(pubKey);
        v3CertGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

        //
        // add the extensions
        //
        v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
            new SubjectKeyIdentifierStructure(pubKey));

        v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
            new AuthorityKeyIdentifierStructure(caPubKey));

        X509Certificate cert = v3CertGen.generateX509Certificate(caPrivKey);

        cert.checkValidity(new Date());

        cert.verify(caPubKey);

        PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;

        //
        // this is also optional - in the sense that if you leave this
        // out the keystore will add it automatically, note though that
        // for the browser to recognise the associated private key this
        // you should at least use the pkcs_9_localKeyId OID and set it
        // to the same as you do for the private key's localKeyId.
        //
        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(
            "部门证书"));
        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
            new SubjectKeyIdentifierStructure(pubKey));

        return cert;
    }
    
    public Certificate generate(String deptId, String deptName, String pass) throws Exception {
            
        if (Security.getProvider("BC") == null)
                Security.addProvider(new BouncyCastleProvider());
            
        char[] passwd = pass.toCharArray(); //pfx����  
        KeyPairGenerator rsaKeyGen = null;
        KeyPair rsaKeyPair = null;
        //System.out.println("Generating a pair of RSA key ... ");
        rsaKeyGen = KeyPairGenerator.getInstance("RSA");
        SecureRandom random = new SecureRandom();
        random.nextBytes(new byte[1]);
        rsaKeyGen.initialize(1024, new SecureRandom());
        rsaKeyPair = rsaKeyGen.genKeyPair();
        RSAPublicKey rsaPublic = (RSAPublicKey) rsaKeyPair.getPublic();
        RSAPrivateKey rsaPrivate = (RSAPrivateKey) rsaKeyPair.getPrivate();
        
        
        Certificate[] chain = new Certificate[2];
        chain[1] = createMasterCert(CryptoUtil.getPublicKey(PropertyLoader
            .get(CAConstants.CA_PUB_KEY01_NAME)), CryptoUtil.getPrivateKey(PropertyLoader
            .get(CAConstants.CA_PRIV_KEY01_NAME)));
        chain[0] = createCert((PublicKey) rsaPublic, CryptoUtil
            .getPrivateKey(PropertyLoader
            .get(CAConstants.CA_PRIV_KEY01_NAME)), CryptoUtil
            .getPublicKey(PropertyLoader
            .get(CAConstants.CA_PUB_KEY01_NAME)), deptId, deptName);

      
        //
        // store the key and the certificate chain
        //
        KeyStore store = KeyStore.getInstance("PKCS12", "BC");

        store.load(null, null);

        //
        // if you haven't set the friendly name and local key id above
        // the name below will be the name of the key
        //
        store.setKeyEntry("部门证书", rsaPrivate, null, chain);
        
        FileOutputStream fOut = new FileOutputStream(deptName + ".p12");
        
        store.store(fOut, passwd);
        
        
        
        return chain[0];
        }

}
